﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Framework.Business;
using Framework.Helper;
using MongoDB.Driver;
using MongoDB.Driver.Linq;
using Nessos.LinqOptimizer.CSharp;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using RealDataReceiver.DataAccess;
using RealDataReceiver.Entities;
using ServiceStack.Text;

namespace RealDataReceiver.Business
{
    public class RealDataReceiveBusiness
    {
        private static readonly ConcurrentDictionary<string, DeviceMetaData> RealDataPool =
            new ConcurrentDictionary<string, DeviceMetaData>();
        public static void RealDataProcess(ServiceContextBase<RealDataReceiverRequest, RealDataReceiverResponse> ctx)
        {
            bool recordFile = ConfigurationHelper.GetBoolean("EnableRecordFile", false);
            ctx.Log(string.Format("Enable record file [{0}]", recordFile));
            if (recordFile)
            {
                var formatMsg = ConvertRawdataToDeviceDataList(ctx);

                var folderName = ConfigurationHelper.GetConfiguration("DataFolder");
                var fileName = ConfigurationHelper.GetConfiguration("FileName");

                if (!Directory.Exists(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, folderName)))
                {
                    Directory.CreateDirectory(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, folderName));
                }
                var fullPath = Path.Combine(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, folderName), fileName);

                WriteToFile(formatMsg, fullPath);

                return;
            }

            RecordNosql(ctx);
        }

        private static void RecordNosql(ServiceContextBase<RealDataReceiverRequest, RealDataReceiverResponse> ctx)
        {
            var data = ConvertRawdataToDeviceDataList(ctx);

            bool isSendMq = ConfigurationHelper.GetBoolean("SendMQ", false);
            ctx.Log($"Is Enable send MQ:[{isSendMq}]");
            if (isSendMq)
            {
                ctx.Log(">>> Start send MQ");
                MessageQueueHelper.Instance.SendMessage(data);
                ctx.Log(">>> Success send MQ");
                return;
            }

            FormatDeviceMetaData(data);

            ctx.Debug(">>> Start to record cloud.");
            RealDataDA.Instance.SaveMany(data);
            ctx.Log("<<< Record successfully.");

        }

        public static void RetrieveRealData(ServiceContextBase<RealDataQueryRequest, RealDataQueryResponse> ctx)
        {
            bool recordFile = ConfigurationHelper.GetBoolean("EnableRecordFile", false);
            ctx.Log($"Enable record file [{recordFile}]");
            if (recordFile)
            {
                var folderName = ConfigurationHelper.GetConfiguration("DataFolder");
                var fileName = ConfigurationHelper.GetConfiguration("FileName");

                if (!Directory.Exists(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, folderName)))
                {
                    var msg = $"Not Found data folder [{folderName}]";
                    ctx.Error(msg);
                    throw new FileNotFoundException(msg);
                }
                var fullPath = Path.Combine(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, folderName), fileName);

                ctx.Log($"Retrieve data from [{fullPath}]");

                var json = File.ReadAllText(fullPath);

                ctx.Response.RawDatas = JsonConvert.DeserializeObject<List<DeviceMetaData>>(json);
                return;
            }

            var enableRecordCache = ConfigurationHelper.GetBoolean("EnableRecordCache", false);

            if (!RealDataPool.IsEmpty && enableRecordCache)
            {
                ctx.Log("   > Start retrieve data from cache.");
                var list = new List<Device>();

                foreach (var data in RealDataPool.Values)
                {
                    if (list.Any(r => r.LocationId == data.LocationId && r.DeviceId == data.DeviceId))
                    {
                        var de = list.First(r => r.LocationId == data.LocationId && r.DeviceId == data.DeviceId);
                        de.Paras.Add(new MetaData
                        {
                            Id = data.PropertyId,
                            Key = data.PropertyName,
                            Value = data.Value,
                            Unit = data.Unit,
                            UpdateTime = data.UpdateTime
                        });
                    }
                    else
                    {
                        var device = new Device
                        {
                            LocationId = data.LocationId,
                            LocationName = data.LocationName,
                            DeviceId = data.DeviceId,
                            DeviceName = data.DeviceName,
                            Module = data.Module,
                            Paras = new List<MetaData>()
                        };
                        device.Paras.Add(new MetaData
                        {
                            Id = data.PropertyId,
                            Key = data.PropertyName,
                            Value = data.Value,
                            Unit = data.Unit,
                            UpdateTime = data.UpdateTime
                        });
                        list.Add(device);
                    }
                    
                }
                ctx.Response.Count = list.Count;
                ctx.Response.Result = list;
                ctx.Log("   > End retrieve data from cache.");
                return;
            }

            var result = QueryRealDataFromNoSql(ctx);

            ctx.Response.Count = result.Count;
            ctx.Response.Result = result;
        }

        public static void ConsumeMessage(ServiceContextBase<MessageConsumeRequest, MessageConsumeResponse> ctx)
        {
            if (ctx.Request.MessagePullCount <= 0)
            {
                var pullCount = ConfigurationHelper.GetInteger("MessagePullCount", 10);
                ctx.Request.MessagePullCount = pullCount;
            }

            var result = MessageQueueHelper.Instance.PullMessage<List<DeviceMetaData>>(ctx.Request.MessagePullCount);

            if(result.IsNullOrEmpty())
                return;

            var dbDatas = new List<DeviceMetaData>();

            foreach (var list in result)
            {
                FormatDeviceMetaData(list);
                dbDatas.AddRange(list);
            }

            var enableRecordCache = ConfigurationHelper.GetBoolean("EnableRecordCache", false);

            if (enableRecordCache)
            {
                foreach (var dbData in dbDatas)
                {
                    var key = dbData.LocationId + "." + dbData.DeviceId + "." + dbData.PropertyId;
                    RealDataPool.AddOrUpdate(key, k => dbData, (k, old) => dbData);
                }
            }

            ctx.Debug(">>> Start to record cloud.");
            RealDataDA.Instance.SaveMany(dbDatas);
            ctx.Log("<<< Record successfully.");

        }

        private static List<Device> QueryRealDataFromNoSql(ServiceContextBase<RealDataQueryRequest, RealDataQueryResponse> ctx)
        {
            ctx.Log("  > Start retrieve data from nosql.");

            if (!ctx.Request.LocationIdList.IsNullOrEmpty())
            {
                ctx.Request.LocationIdList = ctx.Request.LocationIdList.Distinct().ToList();
            }

            var datas = RealDataDA.Instance.GetCollection().AsQueryable(new AggregateOptions { AllowDiskUse = true });
            if (!ctx.Request.LocationIdList.IsNullOrEmpty())
            {
                datas = Queryable.Where(datas, r => ctx.Request.LocationIdList.Contains(r.LocationId)) as IMongoQueryable<DeviceMetaData>;
            }
            if (!ctx.Request.DeviceIdList.IsNullOrEmpty() && datas != null)
            {
                datas = Queryable.Where(datas, r => ctx.Request.DeviceIdList.Contains(r.DeviceId)) as IMongoQueryable<DeviceMetaData>;
            }

            int dateGap = ConfigurationHelper.GetInteger("HoursForAvaliableData", 0);
            if (dateGap > 0)
            {
                datas = datas.Where(r => r.UpdateTime > DateTime.Now.AddHours(-1 * dateGap));
            }

           // datas.AsQueryExpr().OrderByDescending(r=>r.UpdateTime).GroupBy()
            var result = Queryable.Select(datas
                .OrderByDescending(t => t.UpdateTime)
                .GroupBy(r => r.Key), g => new DeviceMetaData
            {
                Id = g.First().Id,
                Key = g.First().Key,
                LocationId = g.First().LocationId,
                LocationName = g.First().LocationName,
                DeviceId = g.First().DeviceId,
                DeviceName = g.First().DeviceName,
                Module = g.First().Module,
                UpdateTime = g.First().UpdateTime,
                PropertyId = g.First().PropertyId,
                PropertyName = g.First().PropertyName,
                Unit = g.First().Unit,
                Value = g.First().Value,
            }).ToList();

            // LocationId.DeviceId
            var gr = result.AsQueryable().GroupBy(t => $"{t.LocationId}.{t.DeviceId}").Select(r => new Device()
            {
                DeviceId = r.First().DeviceId,
                Module = r.First().Module,
                DeviceName = r.First().DeviceName,
                LocationId = r.First().LocationId,
                LocationName = r.First().LocationName,
                Paras = r.Select(t => new MetaData
                {
                    Id = t.PropertyId,
                    Key = t.PropertyName,
                    Value = t.Value,
                    Unit = t.Unit,
                    UpdateTime = t.UpdateTime
                }).ToList()
            }).ToList();

            ctx.Log("  > End retrieve data from nosql.");
            return gr;
        }

        public static void RetrieveHistoryData(ServiceContextBase<HistoryDataQueryRequest, HistoryDataQueryResponse> ctx)
        {
            ctx.Log("Retrieve history data from nosql.");

            var datas = RealDataDA.Instance.GetCollection().AsQueryable(new AggregateOptions { AllowDiskUse = true });
            if (!string.IsNullOrEmpty(ctx.Request.LocationId))
            {
                datas = Queryable.Where(datas, r => ctx.Request.LocationId == r.LocationId) as IMongoQueryable<DeviceMetaData>;
            }
            if (!string.IsNullOrEmpty(ctx.Request.DeviceId) && datas != null)
            {
                datas = Queryable.Where(datas, r => ctx.Request.DeviceId == r.DeviceId) as IMongoQueryable<DeviceMetaData>;
            }

            if (null == datas || !datas.Any())
            {
                ctx.Warn("No records");
                return;
            }

            ctx.Response.LocationName = datas.First().LocationName;
            ctx.Response.DeviceName = datas.First().DeviceName;

            var gr = datas.AsQueryable().GroupBy(t => t.PropertyId).Select(r => new KpiData
            {
                Id = r.Key,
                Name = r.First().PropertyName,
                Datas = r.Select(g => new MetaData
                {
                    Value = g.Value,
                    Unit = g.Unit,
                    UpdateTime = g.UpdateTime
                }).ToList()
            }).ToList();

            ctx.Response.Kpis = gr;
            ctx.Response.Count = gr.Count;
        }

        private static void WriteToFile(List<DeviceMetaData> msg, string filePath)
        {
            var jsonString = JsonConvert.SerializeObject(msg);
            byte[] textByte = Encoding.UTF8.GetBytes(jsonString);
            using (FileStream strm = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.Write))
            {
                strm.Write(textByte, 0, textByte.Length);
            }
        }

        private static List<DeviceMetaData> ConvertRawdataToDeviceDataList(ServiceContextBase<RealDataReceiverRequest, RealDataReceiverResponse> ctx)
        {
            ctx.Log(">>> Start to convert raw data.");
            var deviceMetaDatas = new List<DeviceMetaData>();

            var dc = JsonConvert.DeserializeObject<dynamic>(ctx.RawInputString);

            string module = string.Empty;
            DateTime updateTime = DateTime.Now;

            foreach (JProperty item in dc)
            {
                if (item.Name == "module")
                {
                    module = item.Value.ToString();
                    continue;
                }

                if (item.Name == "data") //property name
                {
                    foreach (JProperty device in item.Value)
                    {
                        foreach (JProperty para in device.Value)
                        {
                            // "14.51"
                            var locationid = device.Name.Split('.').First();

                            if (!IsLocationAvaliable(locationid))
                                continue;

                            DeviceMetaData d = new DeviceMetaData
                            {
                                Module = module,
                                UpdateTime = updateTime,
                            };
                            d.DeviceId = device.Name;
                            d.PropertyId = para.Name;
                            d.Key = HashHelper.Hash(device.Name + para.Name);
                            d.Value = para.Value.ToString();

                            deviceMetaDatas.Add(d);
                        }
                    }
                }

            }
            ctx.Log("<<< Convert raw data successfully.");
            return deviceMetaDatas;
        }

        private static void FormatDeviceMetaData(List<DeviceMetaData> deviceMetaDatas)
        {
            if (deviceMetaDatas.IsNullOrEmpty())
                return;
            
            var locationId = deviceMetaDatas.First().DeviceId.Split('.').First();
            var localChannel = ChannelDA.Instance.GetAllChannels().FirstOrDefault(l => l.ChannelId == locationId);
            if(localChannel == null)
                return;
            foreach (var deviceMetaData in deviceMetaDatas)
            {
                var deviceId = deviceMetaData.DeviceId.Split('.').Last();
                var device = localChannel.DeviceList.FirstOrDefault(d => d.DeviceId == deviceId);
                if(device == null)
                    continue;
                var signal = device.SignalList.FirstOrDefault(s => s.SignalId == deviceMetaData.PropertyId);
                if (signal == null)
                    continue;
                deviceMetaData.LocationId = locationId;
                deviceMetaData.LocationName = localChannel.ChannelName;
                deviceMetaData.DeviceId = deviceId;
                deviceMetaData.DeviceName = device.DeviceName;
                
                deviceMetaData.PropertyName = signal.SignalName;
                deviceMetaData.Unit = signal.Unit;
            }
        }
        
        private static bool IsLocationAvaliable(string locationId)
        {
            var ids = ConfigurationHelper.GetConfiguration("LocationIds");
            // not config 
            if (string.IsNullOrWhiteSpace(ids))
                return true;
            var splitIds = ConfigurationHelper.GetConfiguration("LocationIds").Split(',').ToList();
            return splitIds.Contains(locationId);
        }
    }
}
